﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text.RegularExpressions;
using System.Threading;
using ggdll.GGMessages;
using System.Reflection;
using System.Xml;

namespace ggdll
{
    public class GG
    {

        public delegate void GGSProtocolStatusEvent();
        public event GGSProtocolStatusEvent Login_Succesfull;
        public event GGSProtocolStatusEvent Login_Failed;

        public delegate void GGContactListEvent(List<GGContacts.GGGroup> groups, List<GGContacts.GGContact> contacts);
        public delegate void GGContactListStatusEvent(List<GGContacts.GGContactState> states);
        public event GGContactListEvent List_Updated;
        public event GGContactListStatusEvent Status_Updates;

        public delegate void GGUserStatusEventHandler(GGContacts.GGContactStatusEnum state);
        public event GGUserStatusEventHandler Status_Changed;

        public delegate void GGIncomingMessageEventHandler(GGMessages.Incoming.GGIncomingMessage msg);
        public event GGIncomingMessageEventHandler Message_Arrived;


        private List<GGContacts.GGGroup> groups = new List<GGContacts.GGGroup>();
        private List<GGContacts.GGContact> contacts = new List<GGContacts.GGContact>();

        private Networking networking;
        public string _uin;
        private string _password;
        private static Thread initThread;
        private static Thread pingThread;

        private GGState ggState = GGState.DISCONNECTED;

        public List<GGContacts.GGContactState> GGContactStateList = new List<GGContacts.GGContactState>();

        public GG(string uid, string password)
        {
            
            _uin = uid;
            _password = password;
            initThread = new Thread(initThread_proc);
            initThread.Start();
        }

        void networking_ReceivedData(byte[] data)
        {
            switch(BitConverter.ToInt32(data,0))
            {
                case (int)GGMessages.GGMessageType.GG_WELCOME:
                    login(data);
                    break;
                case (int)GGMessages.GGMessageType.GG_LOGIN80_OK:
                    login_success();
                    break;
                case (int)GGMessages.GGMessageType.GG_LOGIN80_FAILED:
                    login_failed();
                    break;
                case (int)GGMessages.GGMessageType.GG_NOTIFY_REPLY80:
                    GGMessages.Incoming.GGMessageNotifyReply msg = new GGMessages.Incoming.GGMessageNotifyReply(data);
                    GGContactStateList = msg.GGcontactstates;
                    Status_Updates(GGContactStateList);
                    break;
                case (int)GGMessages.GGMessageType.GG_USERLIST100_REPLY:
                    getContactList(data);
                    notifyContacts();
                    Status_Updates(GGContactStateList);
                    break;
                case (int)GGMessages.GGMessageType.GG_STATUS80:
                    statusChange(new GGMessages.Incoming.GGMessageNotifyReply(data));
                    break;
                case (int)GGMessages.GGMessageType.GG_RECV_MSG80:
                    GGMessages.Incoming.GGIncomingMessage msg2 = new GGMessages.Incoming.GGIncomingMessage(data);
                    Message_Arrived(msg2);
                    GGMessages.Outgoing.GGMessageIncomingMessageConfirm msg3 = new GGMessages.Outgoing.GGMessageIncomingMessageConfirm();
                    msg3.seq = msg2.seq;
                    networking.send(msg3.getBytes());
                    break;
                default:
                    break;
            }
        }

        public void sendMessage(int uin, string message)
        {
            GGMessages.Outgoing.GGMessageOutgoingMessage msg = new GGMessages.Outgoing.GGMessageOutgoingMessage();
            msg.plain_message = message;
            msg.recipient = uin;
            networking.send(msg.getBytes());
        }

        public void notifyContacts()
        {
            GGMessages.Outgoing.GGMessageNotifyContacts msg2 = new GGMessages.Outgoing.GGMessageNotifyContacts();
            msg2.contacts = contacts;
            networking.send(msg2.getBytes());
        }
        public void notifyEmptyContactList()
        {
            if (ggState == GGState.CONNECTED)
            {
                GGMessages.Outgoing.GGMessageNotifyEmptyContactList msg = new GGMessages.Outgoing.GGMessageNotifyEmptyContactList();
                networking.send(msg.getBytes());
            }
        }

        private void statusChange(GGMessages.Incoming.GGMessageNotifyReply msg)
        {
            foreach (GGContacts.GGContactState it in msg.GGcontactstates)
            {
                if (it.uin.ToString() == _uin)
                {
                    Status_Changed((GGContacts.GGContactStatusEnum)(it.status & 0x00FF));
                    return;
                }
            }
            Status_Updates(msg.GGcontactstates);
        }

        private void getContactList(byte[] data)
        {
            int size = BitConverter.ToInt32(data, 4);
            int version = BitConverter.ToInt32(data, 10);
            byte[] _dat = new byte[size-7];
            System.Buffer.BlockCopy(data, 15, _dat, 0, size - 7);

            byte[] decompressed = StaticFunctions.Decompress(_dat);
            string xml = StaticVars.enc.GetString(decompressed);


            contacts = new List<GGContacts.GGContact>();
            groups = new List<GGContacts.GGGroup>();


            using (XmlReader reader = XmlReader.Create(new StringReader(xml)))
            {
                GGContacts.GGContact tempcnt = new GGContacts.GGContact();
                GGContacts.GGGroup tempgrp = new GGContacts.GGGroup();
                while (reader.Read())
	            {
                    if(reader.IsStartElement())
                    {
                        switch(reader.Name)
                        {
                            case "Group":
                                tempgrp = new GGContacts.GGGroup();
                                break;
                            case "Id":
                                reader.Read();
                                tempgrp.GUID=new Guid(reader.Value);
                                break;
                            case "Name":
                                reader.Read();
                                tempgrp.Name = reader.Value;
                                break;
                            case "IsExpanded":
                                reader.Read();
                                if (reader.Value == "true") tempgrp.IsExpanded = true;
                                else tempgrp.IsExpanded = false;
                                break;
                            case "IsRemovable":
                                reader.Read();
                                if (reader.Value == "true") tempgrp.IsRemovable = true;
                                else tempgrp.IsRemovable = false;
                                break;
                            case "Contact":
                                tempcnt = new GGContacts.GGContact();
                                break;
                            case "Guid":
                                reader.Read();
                                tempcnt.GUID=new Guid(reader.Value);
                                break;
                            case "GGNumber":
                                reader.Read();
                                tempcnt.Uin=Int32.Parse(reader.Value);
                                break;
                            case "ShowName":
                                reader.Read();
                                tempcnt.ShowName=reader.Value;
                                break;
                            case "FirstName":
                                reader.Read();
                                tempcnt.FirstName=reader.Value;
                                break;
                            case "GroupId":
                                reader.Read();
                                tempcnt.GroupsGUIDs.Add(new Guid(reader.Value));
                                break;
                            case "CurrentAvatar":
                                reader.Read();
                                tempcnt.CurrentAvatar=Int32.Parse(reader.Value);
                                break;
                            case "URL":
                                reader.Read();
                                tempcnt.AvatarsURLS.Add(reader.Value);
                                break;
                            case "FlagBuddy":
                                reader.Read();
                                if(reader.Value=="true") tempcnt.FlagBuddy=true;
                                else tempcnt.FlagBuddy=false;
                                break;
                             case "FlagNormal":
                                reader.Read();
                                if(reader.Value=="true") tempcnt.FlagNormal=true;
                                else tempcnt.FlagNormal=false;
                                break;
                            case "FlagFriend":
                                reader.Read();
                                if(reader.Value=="true") tempcnt.FlagFriend=true;
                                else tempcnt.FlagFriend=false;
                                break;
                            default:
                                break;
                        }
                    }
                    else if (reader.NodeType == XmlNodeType.EndElement && reader.Name=="Contact")
                    {
                        contacts.Add(tempcnt);
                    }
                    else if (reader.NodeType == XmlNodeType.EndElement && reader.Name == "Group")
                    {
                        groups.Add(tempgrp);
                    }

                }
            }

            List_Updated(groups, contacts);

        }



        private void login_failed()
        {
            Login_Failed();
            ggState = GGState.DISCONNECTED;
        }

        private void login_success()
        {
            ggState = GGState.CONNECTED;
            pingThread = new Thread(pingThread_proc);
            pingThread.Start();
            Login_Succesfull();
            
        }


        private IPEndPoint getServerIP()
        {
            byte[] buffer = new byte[4096];
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://appmsg.gadu-gadu.pl/appsvc/appmsg_ver8.asp?fmnumber=13346677&fmt=2&version=8.0.0.7669");
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            Stream receiveStream = response.GetResponseStream();
            receiveStream.Read(buffer, 0, buffer.Length);
            System.Text.Encoding enc = System.Text.Encoding.ASCII;
            string sPattern = @"(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
            Match match = Regex.Match(enc.GetString(buffer), sPattern);
            return new IPEndPoint(IPAddress.Parse(match.Value), 8074);
        }

        private void login(byte[] data, GGContacts.GGContactStatusEnum status = GGContacts.GGContactStatusEnum.GG_STATUS_AVAIL)
        {
            byte[] response = new byte[147];

            byte[] salt = new byte[4];
            System.Buffer.BlockCopy(data, 8, salt, 0, 4);
            byte[] password = StaticVars.enc.GetBytes(_password);

            byte[] concat = new byte[salt.Length + _password.Length];
            System.Buffer.BlockCopy(password, 0, concat, 0, password.Length);
            System.Buffer.BlockCopy(salt, 0, concat, _password.Length, salt.Length);

            SHA1CryptoServiceProvider cryptoTransformSHA1 = new SHA1CryptoServiceProvider();
            byte[] sha1 = cryptoTransformSHA1.ComputeHash(concat);
            byte[] hash = new byte[64];        
            System.Buffer.BlockCopy(sha1, 0, hash, 0, sha1.Length);

            GGMessages.Outgoing.GGMessageLogin msg = new GGMessages.Outgoing.GGMessageLogin();
            msg.uin = Int32.Parse(_uin);
            msg.language = "pl";
            msg.hash_type = 0x02;
            msg.hash = hash;
            msg.status = (int)status;
            msg.flags = 0x0000;
            msg.features = 0x0157;
            msg.image_size = 255;
            msg.version = "Scoot " + Assembly.GetExecutingAssembly().GetName().Version;


            networking.send(msg.getBytes());
            return;
        }

        public void requestContactList()
        {
            if (ggState == GGState.CONNECTED)
            {
                GGMessages.Outgoing.GGMessageContactListRequest msg = new GGMessages.Outgoing.GGMessageContactListRequest();
                msg.type = 0x02;
                msg.version = 0;
                msg.format_type = 0x02;
                networking.send(msg.getBytes());
            }
        }

        public void requestStatusChange(GGContacts.GGContactStatusEnum status, string description)
        {
            if (ggState == GGState.CONNECTED)
            {
                GGMessages.Outgoing.GGMessageNewStatus msg = new GGMessages.Outgoing.GGMessageNewStatus();
                msg.flags = 0x0000;
                msg.description = description;
                msg.status = (int)status;
                networking.send(msg.getBytes());
            }
            else if (status != GGContacts.GGContactStatusEnum.GG_STATUS_NOT_AVAIL)
            {
                initThread = new Thread(initThread_proc);
                initThread.Start();
            }
        }

        public void Dispose()
        {
            if (initThread.IsAlive) initThread.Abort();
            try
            {
                if (pingThread.IsAlive) pingThread.Abort();
            }
            catch { }
            try
            {
                networking.Dispose();
            }
            catch { }
        }

        


        private void initThread_proc()
        {
            networking = new Networking(getServerIP());
            networking.ConnectionClosed += new Networking.ReceiverStateHandler(networking_ConnectionClosed);
            networking.ReceivedData += new Networking.ReceivedDataHandler(networking_ReceivedData);
        }

        void networking_ConnectionClosed()
        {
            ggState = GGState.DISCONNECTED;
            Dispose();
        }

        private void pingThread_proc()
        {
            while (true)
            {
                Thread.Sleep(new TimeSpan(0, 1, 0));
                GGMessages.Outgoing.GGMessagePing msg = new GGMessages.Outgoing.GGMessagePing();
                networking.send(msg.getBytes());                
            }
        }
    }
}
